package com.guo.redis.business.hello.service.impl;

import com.guo.redis.business.hello.service.IDistributeLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

/**
 * Redis 实现 可重入锁，不续租
 */
@Service("redisDistributeLockServiceImpl")
public class RedisDistributeLockServiceImpl implements IDistributeLockService {

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @Override
    public boolean tryLock(String key, String value, long time, TimeUnit timeUnit) {
        boolean alreadyAcquiredLock = fastReentrantLockCheck(key, value);
        if(alreadyAcquiredLock) return true;
        return redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit);
    }

    @Override
    public boolean tryLock(String key, String value, long time, TimeUnit timeUnit, long timeOut) {
        long beginTime = System.currentTimeMillis();
        boolean alreadyAcquiredLock = fastReentrantLockCheck(key, value);
        if(alreadyAcquiredLock) return true;
        while (true) {
            boolean b = tryLock(key, value, time, timeUnit);
            if (b) return true;

            long diff = System.currentTimeMillis() - beginTime;
            if (diff >= timeOut) {
                return false;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {

            }
        }
    }

    private boolean fastReentrantLockCheck(String key, String value){
        Serializable serializable = redisTemplate.opsForValue().get(key);
        if(serializable!=null && serializable.toString().equals(value)) {
            //续租
            return true;
        }
        return false;
    }

    @Override
    public boolean unlock(String key, String value) {
        String script ="if redis.call('get',KEYS[1]) == ARGV[1] then" +
                        "   return redis.call('del',KEYS[1]) " +
                        "else" +
                        "   return 0 " +
                        "end";
        Long execute = redisTemplate.execute((RedisCallback<Long>) connection -> connection.scriptingCommands().eval(script.getBytes(), ReturnType.INTEGER, 1, key.getBytes(), value.getBytes()));
        return execute == 1;
    }
}
